using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Serialization;

namespace Common.Dispatcher
{
    /// <summary>
    /// all instructions must have component id (associated design time component),
    /// instance id (also called job ticket id or worker instance id, points to worker instance
    /// at run time).
    /// </summary>
    [Serializable]
    public abstract class InstructionBase: ICloneable
    {
        #region Fields & Properties 
        private Guid _OwnerWorkerInstanceID;

        /// <summary>worker instance id, can retrieve thread, job status, etc.</summary>
        public Guid OwnerWorkerInstanceID
        {
            get { return _OwnerWorkerInstanceID; }
            set { _OwnerWorkerInstanceID = value; }
        }

        private Guid _OwnerComponentID;

        /// <summary>design time activity</summary>
        public Guid OwnerComponentID
        {
            get { return _OwnerComponentID; }
            set { _OwnerComponentID = value; }
        }

        private DateTime _SubmitTime;
        /// <summary>time picked by job dispatcher</summary>
        public DateTime SubmitTime
        {
            get
            {
                return this._SubmitTime;
            }
            set
            {
                this._SubmitTime = value;
            }
        }
        /// <summary>start time of worker</summary>
        public DateTime StartTime
        {
            get { return _StartTime; }
            set { _StartTime = value; }
        }
        private DateTime _StartTime;

        #endregion

        #region Constructor
        /// <summary>
        /// 
        /// </summary>
        public InstructionBase():this(Guid.Empty,Guid.Empty)
        {
        }

        /// <summary>
        /// constructor, all derived class must call default constructor to initialize
        /// default settings.
        /// </summary>
        public InstructionBase(Guid componentID, Guid jobTicketID)
        {
            this._OwnerComponentID = componentID;
            this._OwnerWorkerInstanceID = jobTicketID;
            this._SubmitTime = DateTime.Now;
            this._StartTime = new DateTime(1900, 1, 1);
        }
        #endregion

        #region xml representation
        /// <summary>
        /// serialize settings to xml
        /// the function below is mainly for storing into database as serialized form instead of string
        /// </summary>
        public virtual XmlNode Serialize(ref XmlDocument xDoc, ref XmlNode parentNode)
        {
            XmlNode xNode = XmlDataUtil.AddElement(ref xDoc, parentNode, "Instruction");
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "Type", this.GetType().FullName);
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "ComponentID", this.OwnerComponentID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "WorkerID", this.OwnerWorkerInstanceID.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "SubmitTime", this.SubmitTime.ToString());
            XmlDataUtil.UpdateAttribute(ref xDoc, xNode, "StartTime", this.StartTime.ToString());

            XmlSerializer xmlSerializer=new XmlSerializer(this.GetType());
            MemoryStream ms=new MemoryStream();
            XmlTextWriter writer=new XmlTextWriter(ms, Encoding.Default);
            xmlSerializer.Serialize(writer, this);
            writer.Flush();
            writer.Close();
            byte[] buffer=new byte[ms.Length];
            ms.Read(buffer, 0, (int) ms.Length);
            ms.Close();
            string data = Encoding.Default.GetString(buffer);
            XmlDataUtil.AddCdataElement(ref xDoc, xNode, "Data", data);
            return xNode;
        }
        /// <summary>
        /// instantiated from xml
        /// "&lt;Instructions&gt;&lt;Instruction&gt;&lt;/Instruction&gt;&lt;/Instruction&gt;", must
        /// instantiate from first child of root node
        /// </summary>
        public virtual InstructionBase Instantiate(XmlNode xNode)
        {
            if (xNode.Attributes.GetNamedItem("Type").Value == this.GetType().FullName)
            {
                string data = XmlDataUtil.GetCdataValue(xNode, "");
                XmlSerializer xmlSerializer = new XmlSerializer(this.GetType());
                XmlReader reader=new XmlTextReader(new StringReader(data));
                return (InstructionBase) xmlSerializer.Deserialize(reader);
            }
            else
            {
                throw new Exception("Unable to instantiate instruction: invalid type specified in xml node");
            }
        }
        #endregion

        #region runtime updates
        /// <summary>
        /// this provide default behavior to give caller a list of fields that can be set at
        /// either design time or run time
        /// </summary>
        public virtual Dictionary<string, Type> GetBindableMemberNames(Type[] excludeMemberFromTypes)
        {
            Dictionary<string, Type> bindableMemberNames = new Dictionary<string, Type>();
            FieldInfo[] fields = this.GetType().GetFields();
            if (fields != null && fields.Length > 0)
            {
                foreach (FieldInfo field in fields)
                {
                    bindableMemberNames.Add(field.Name, field.FieldType);
                }
            }
            PropertyInfo[] props = this.GetType().GetProperties();
            if (props != null && props.Length > 0)
            {
                foreach (PropertyInfo prop in props)
                {
                    bindableMemberNames.Add(prop.Name, prop.PropertyType);
                }
            }

            if (excludeMemberFromTypes != null && excludeMemberFromTypes.Length > 0)
            {
                foreach (Type excludedType in excludeMemberFromTypes)
                {
                    FieldInfo[] exFields = excludedType.GetFields();
                    if (exFields != null && exFields.Length > 0)
                    {
                        foreach (FieldInfo field in exFields)
                        {
                            if (bindableMemberNames.ContainsKey(field.Name))
                                bindableMemberNames.Remove(field.Name);
                        }
                    }
                    PropertyInfo[] exProps = excludedType.GetProperties();
                    if (exProps != null && exProps.Length > 0)
                    {
                        foreach (PropertyInfo prop in exProps)
                        {
                            if (bindableMemberNames.ContainsKey(prop.Name))
                                bindableMemberNames.Remove(prop.Name);
                        }
                    }
                }
            }

            return bindableMemberNames;
        }

        /// <summary>provide a default behavior to read field value</summary>
        public virtual object ReadMemberValue(string memberName)
        {
            FieldInfo fieldInfo = this.GetType().GetField(memberName);
            if (fieldInfo != null)
            {
                return fieldInfo.GetValue(this);
            }
            else
            {
                PropertyInfo propInfo = this.GetType().GetProperty(memberName);
                if (propInfo != null)
                {
                    return propInfo.GetValue(this, null);
                }
            }
            return null;
        }

        /// <summary>provide a default behavior to set field value</summary>
        public virtual void SetMemberValue(string memberName, object memberValue)
        {
            MemberInfo[] members = this.GetType().GetMember(memberName);
            if(members !=null && members.Length==1)
            {
                MemberInfo member = members[0];
                if(member.MemberType==MemberTypes.Field)
                {
                    ((FieldInfo)member).SetValue(this,memberValue);
                }
                else if(member.MemberType==MemberTypes.Property)
                {
                    ((PropertyInfo)member).SetValue(this,memberValue,null);
                }
            }
        }

        ///// <summary>
        ///// using reflection to match gvar name to var place holder 
        ///// when global variable is not string type, prop value should be "${gvarname}" only 
        ///// when global variable is string type, prop value can be sth like "select * from table ${tblName}"
        ///// </summary>
        ///// <param name="gVars"></param>
        //public virtual void UpdateSettingsFromGlobalVariables(List<GlobalVariable> gVars)
        //{
        //    Type instructionType = this.GetType();
        //    string placeHolderPattern = @"\$\{(\S+)\}";
        //    Dictionary<string, Type> bindableMembers = this.GetBindableMemberNames(null);
        //    if (bindableMembers != null && bindableMembers.Count > 0)
        //    {
        //        foreach (string memberName in bindableMembers.Keys)
        //        {
        //            MemberInfo[] members = instructionType.GetMember(memberName);
        //            if (members != null && members.Length == 1)
        //            {
        //                MemberInfo member = members[0];
        //                if (member.MemberType == MemberTypes.Property)
        //                {
        //                    PropertyInfo prop = member as PropertyInfo;
        //                    if (prop.CanWrite && prop.GetValue(this, null) != null)
        //                    {
        //                        string propVal = prop.GetValue(this, null).ToString();
        //                        if (Regex.IsMatch(propVal, placeHolderPattern))
        //                        {
        //                            string placeHolderName = Regex.Match(propVal, placeHolderPattern).Groups[1].Value;
        //                            foreach (GlobalVariable gVar in gVars)
        //                            {
        //                                if (gVar.VarName == placeHolderName && gVar.VarValue != null)
        //                                {
        //                                    string gVarValue = gVar.VarValue.ToString();
        //                                    if (propVal == "${" + placeHolderName + "}")
        //                                    {
        //                                        prop.SetValue(this, gVar.VarValue, null);
        //                                    }
        //                                    else if (gVar.DataType == SimpleDataType.TextType)
        //                                    {
        //                                        string newPropValue = propVal.Replace(
        //                                            "@{" + placeHolderName + "}", gVarValue);
        //                                        prop.SetValue(this, newPropValue, null);
        //                                    }
        //                                    break;
        //                                }
        //                            }
        //                        }
        //                    }
        //                }
        //                else if (member.MemberType == MemberTypes.Field)
        //                {
        //                    FieldInfo field = member as FieldInfo;
        //                    if (field.GetValue(this) != null)
        //                    {
        //                        string fieldVal = field.GetValue(this).ToString();
        //                        if (Regex.IsMatch(fieldVal, placeHolderPattern))
        //                        {
        //                            string placeHolderName = Regex.Match(fieldVal, placeHolderPattern).Groups[1].Value;
        //                            foreach (GlobalVariable gVar in gVars)
        //                            {
        //                                if (gVar.VarName == placeHolderName && gVar.VarValue != null)
        //                                {
        //                                    string gVarValue = gVar.VarValue.ToString();
        //                                    if (fieldVal == "${" + placeHolderName + "}")
        //                                    {
        //                                        field.SetValue(this, gVar.VarValue);
        //                                    }
        //                                    else if (gVar.DataType == SimpleDataType.TextType)
        //                                    {
        //                                        string newPropValue = fieldVal.Replace(
        //                                            "@{" + placeHolderName + "}", gVarValue);
        //                                        field.SetValue(this, newPropValue);
        //                                    }
        //                                    break;
        //                                }
        //                            }
        //                        }
        //                    }
        //                }
        //            }
        //        }
        //    }
        //}

        #endregion

        #region Implementation of ICloneable

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public object Clone()
        {
            InstructionBase newInstruction = (InstructionBase)Activator.CreateInstance(this.GetType());
            try
            {
                PropertyInfo[] props = this.GetType().GetProperties();
                foreach (PropertyInfo prop in props)
                {
                    if (prop.CanRead && prop.CanWrite)
                    {
                        object propValue = prop.GetValue(this, null);
                        prop.SetValue(newInstruction, propValue, null);
                    }
                }
            }
            catch (Exception ex)
            {
                ExceptionHandler.HandlesException(ex);
            }
            return newInstruction;
        }

        #endregion

        #region global variable bindable
        /// <summary>
        /// 
        /// </summary>
        /// <param name="gvars"></param>
        public void BindToGlobalVariableValues(List<GlobalVariable> gvars)
        {
            if(gvars==null || gvars.Count==0)
                return;
            Dictionary<string, Type> bindableMemberTypes =
                this.GetBindableMemberNames(new Type[] {typeof (InstructionBase)});
            if(bindableMemberTypes==null || bindableMemberTypes.Count==0)
                return;
            Dictionary<string,GlobalVariable> globalVariables=new Dictionary<string, GlobalVariable>();
            foreach(GlobalVariable gVar in gvars)
            {
                if (!globalVariables.ContainsKey(gVar.VarName))
                    globalVariables.Add(gVar.VarName, gVar);
            }

            foreach(string memberName in bindableMemberTypes.Keys)
            {
                object memberValue = this.ReadMemberValue(memberName);
                if(memberValue==null)
                    continue;

                string memberValString = memberValue.ToString();
                string memberValReplaced = memberValString;
                Regex placeHolderRegex=new Regex(@"\$\{(\S+)\}");
                Match placeHolderMatch = placeHolderRegex.Match(memberValString);
                bool updateMemberValue = false;
                while (placeHolderMatch.Success)
                {
                    string placeHolder = placeHolderMatch.Groups[1].Value;
                    if(globalVariables.ContainsKey(placeHolder))
                    {
                        GlobalVariable gvar = globalVariables[placeHolder];
                        if (placeHolderMatch.Value.Length == memberValString.Length)
                        {
                            object gValue = SimpleDataTypeUtil.ToDbValue(gvar.DataType, gvar.VarValue);
                            this.SetMemberValue(memberName,gValue);
                            break;
                        }
                        else
                        {
                            updateMemberValue = true;
                            memberValReplaced = memberValReplaced.Replace(placeHolderMatch.Value, gvar.VarValue);
                        }
                    }
                    
                    placeHolderMatch = placeHolderMatch.NextMatch();
                }

                if (updateMemberValue)
                {
                    this.SetMemberValue(memberName,memberValReplaced);
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="gvars"></param>
        public void ReplaceWithGlobalVariableBindings(List<GlobalVariable> gvars)
        {
            if (gvars == null || gvars.Count == 0)
                return;
            Dictionary<string, Type> bindableMemberTypes =
                this.GetBindableMemberNames(new Type[] { typeof(InstructionBase) });
            if (bindableMemberTypes == null || bindableMemberTypes.Count == 0)
                return;
            Dictionary<string, GlobalVariable> globalVariables = new Dictionary<string, GlobalVariable>();
            foreach (GlobalVariable gVar in gvars)
            {
                if (!string.IsNullOrEmpty(gVar.VarValue) && !globalVariables.ContainsKey(gVar.VarValue))
                    globalVariables.Add(gVar.VarValue, gVar);
            }

            foreach (string memberName in bindableMemberTypes.Keys)
            {
                object memberValue = this.ReadMemberValue(memberName);
                if (memberValue == null)
                    continue;

                string memberValString = memberValue.ToString();
                if(globalVariables.ContainsKey(memberValString))
                {
                    GlobalVariable gvar = globalVariables[memberValString];
                    if(gvar.DataType==SimpleDataType.TextType)
                    {
                        string placeHolder = "${" + gvar.VarName + "}";
                        this.SetMemberValue(memberName, placeHolder);
                    }
                }
            }
        }
        #endregion
    }
}
